<div id='debug'></div>
<script>

Array.prototype.random = function()
{
	return this[Math.floor(Math.random() * this.length)];
};

function dp2dim(arr)
{
	if (!console) {
		return;
	}
	console.log(arr.length + 'x' + arr.length);
	for (var y = 0; y < arr.length; y++) {
		var pool = [];
		for (var x = 0; x < arr[y].length; x++) {
			pool.push('(' + x + ',' + y + ')=' + arr[y][x]);
		}
		console.log(pool.join(', '));
	}
}

function surface(in_size, in_color)
{
	this.piece = [];
	for (var y = 0; y < in_size; y++) {
		this.piece[y] = [];
		for (var x = 0; x < in_size; x++) {
			this.piece[y][x] = in_color;
		}
	}
	this.edges = {};
	var dirs = ['T', 'R', 'B', 'L'];
	for (var i = 0; i < dirs.length; i++) {
		this.edges[dirs[i]] = {
			surface : null,
			edge : null
		};
	}
}

var surfacePrototypes = {
	attach : function(srcDir, dstDir, dstSurface) {
		var srcSurface = this;
		srcSurface.edges[srcDir] = {
			surface : dstSurface,
			edge : dstDir
		};
		dstSurface.edges[dstDir] = {
			surface : srcSurface,
			edge : srcDir
		};
	},
	copyPiece : function(in_left_turn) {
		var ret = [];
		var size = this.piece.length;
		var max = size - 1;
		for (var y = 0; y < size; y++) {
			ret[y] = [];
			for (var x = 0; x < size; x++) {
				/*
					default :
						1 2 3
						4 5 6
						7 8 9
				*/
				switch (in_left_turn) {
				case 1 :
					/*
						7 4 1
						8 5 2 --> default
						9 6 3
					*/
					ret[y][x] = this.piece[x][max - y];
					break;
				case 2 :
					/*
						9 8 7
						6 5 4 --> default
						3 2 1
					*/
					ret[y][x] = this.piece[max - y][max - x];
					break;
				case 3 :
					/*
						3 6 9
						2 5 8 --> default
						1 4 7
					*/
					ret[y][x] = this.piece[max - x][y];
					break;
				default :
					ret[y][x] = this.piece[y][x];
					break;
				}
			}
		}
		return ret;
	},
	getXYList : function(in_dir, in_col) {
		var size = this.piece.length;
		var getPiece = function(i) {
			return {
				T : {
					x : in_col,
					y : i
				},
				R : {
					x : (size - 1) - i,
					y : in_col
				},
				B : {
					x : in_col,
					y : (size - 1) - i
				},
				L : {
					x : i,
					y : in_col
				}
			}[in_dir];
		};
		var ret = [];
		for (var i = 0; i < size; i++) {
			ret.push(getPiece(i));
		}
		return ret;
	},
	getNextSurface : function(in_dir) {
		var nextEdge = {
			T : 'B',
			R : 'L',
			B : 'T',
			L : 'R'
		}[this.edges[in_dir].edge];
		return {
			su : this.edges[in_dir].surface,
			di : nextEdge
		};
	},
	getRoundSurfaces : function(in_dir) {
		var ret = [{su : this, di : in_dir}];
		var next = null;
		while (true) {
			with (ret[ret.length - 1]) {
				next = su.getNextSurface(di);
			}
			if (next.su == this) {
				break;
			} else {
				ret.push(next);
			}
		}
		return ret;
	},
	leftRotate : function(in_delta) {
		this.piece = this.copyPiece(in_delta);
	}
};

for (var prop in surfacePrototypes) {
	surface.prototype[prop] = surfacePrototypes[prop];
}

var cube = {
	dim : 6,
	/* surfaces per round */
	spr : 4,
	size : -1,
	surfaces : [],
	createCube : function(in_size) {
		this.size = in_size;
		for (var i = 0; i < this.dim; i++) {
			this.surfaces[i] = new surface(this.size, i);
		}
		/*
					(5)
					1,2
					3,4

				(3) (4)
				1,2 2,1
				3,4 3,4

			(1) (2)
			1,2 1,2
			3,4 3,4

			(0)
			1,2
			3,4
		*/
		var s = this.surfaces;
		// top-line
		s[1].attach('T', 'L', s[3]);
		s[3].attach('T', 'L', s[5]);
		s[5].attach('T', 'L', s[1]);
		// center-line
		s[0].attach('T', 'B', s[1]);
		s[1].attach('R', 'L', s[2]);
		s[2].attach('T', 'B', s[3]);
		s[3].attach('R', 'L', s[4]);
		s[4].attach('T', 'B', s[5]);
		s[5].attach('R', 'L', s[0]);
		// bottom-line
		s[0].attach('R', 'B', s[2]);
		s[2].attach('R', 'B', s[4]);
		s[4].attach('R', 'B', s[0]);
	},
	rotate1 : function(in_dim, in_dir, in_col, in_delta) {
		var surfaces = this.surfaces[in_dim].getRoundSurfaces(in_dir);
		var tmp = [];
		for (var i = 0; i < surfaces.length; i++) {
			with (surfaces[i]) {
				tmp.push({
					pi : su.copyPiece(0),
					xy : su.getXYList(di, in_col)
				});
			}
		}
		for (var i = 0; i < surfaces.length; i++) {
			// source
			var src = i;
			var spi = tmp[src].pi;
			var sxy = tmp[src].xy;
			// destination
			var dst = (i + in_delta) % surfaces.length;
			var dpi = surfaces[dst].su.piece;
			var dxy = tmp[dst].xy;
			// rotate
			for (var j = 0; j < sxy.length; j++) {
				dpi[dxy[j].y][dxy[j].x] = spi[sxy[j].y][sxy[j].x];
			}
		}
	},
	rotate2 : function(in_dim, in_dir, in_col, in_delta) {
		var ret = (function(self) {
			switch (in_dir + in_col) {
			case 'B0' :
				/*
					| 2 3
					| 5 6
					v 8 9
				*/
				return ['L', self.spr - in_delta];
			case 'R0' :
				/*
					---->
					4 5 6
					7 8 9
				*/
				return ['T', in_delta];
			case 'B' + (self.size - 1) :
				/*
					1 2 |
					4 5 |
					7 8 v
				*/
				return ['R', in_delta];
			case 'R' + (self.size - 1) :
				/*
					1 2 3
					4 5 6
					---->
				*/
				return ['B', self.spr - in_delta];
			default :
				return null;
			}
		})(this);
		if (ret) {
			(this.surfaces[in_dim].getNextSurface(ret[0])).su.leftRotate(ret[1]);
		}
	},
	rotate : function(in_dim, in_xy, in_col, in_delta) {
		/*
			- in_dim : surface id
			- in_xy :
				- true : x
				- false : y
			- in_col : 0 ~ (this.size - 1)
			- in_delta : move-count
		*/
		var dir = in_xy ? 'B' : 'R';
		var delta = in_delta % this.spr;
		if (delta < 0) {
			delta = this.spr + delta;
		}
		this.rotate1(in_dim, dir, in_col, delta);
		this.rotate2(in_dim, dir, in_col, delta);
	},
	leftRotate : {'TR' : 1, 'LB' : 3, 'BL' : 1, 'RT' : 3},
	getPieceData4View : function(in_dim, in_dim_name) {
		var ret = {};
		ret[in_dim_name] = this.surfaces[in_dim].piece;
		var dirs = ['T', 'R', 'B', 'L'];
		while (dirs.length > 0) {
			var dir = dirs.pop();
			var next = this.surfaces[in_dim].getNextSurface(dir);
			if (typeof(this.leftRotate[dir + next.di]) == 'undefined') {
				ret[dir] = next.su.copyPiece(0);
			} else {
				ret[dir] = next.su.copyPiece(this.leftRotate[dir + next.di]);
			}
		}
		return ret;
	},
	dir2dim : function(in_dim, in_dir) {
		var next = this.surfaces[in_dim].getNextSurface(in_dir);
		for (var i = 0; i < this.surfaces.length; i++) {
			if (this.surfaces[i] == next.su) {
				if (typeof(this.leftRotate[in_dir + next.di]) == 'undefined') {
					return {dim : i, lro : 0};
				} else {
					return {dim : i, lro : this.leftRotate[in_dir + next.di]};
				}
			}
		}
		return null;
	},
	shuffle : function(in_cnt) {
		var dim = [];
		for (var i = 0; i < this.dim; i++) {
			dim[i] = i;
		}
		var xy = [true, false];
		var col = [];
		for (var i = 0; i < this.size; i++) {
			col[i] = i;
		}
		var delta = [1, 2, 3];
		while (in_cnt-- > 0) {
			cube.rotate(dim.random(), xy.random(), col.random(), delta.random());
		}
	}
};

function cPosList(x, y, v1, v2)
{
	// assume LT --> TR --> RB --> BL
	this.v = [v1, v2];
	this.pos = [
		{p : [x,               y]},
		{p : [x + v1.x,        y + v1.y]},
		{p : [x + v1.x + v2.x, y + v1.y + v2.y]},
		{p : [x        + v2.x, y        + v2.y]}
	];
	for (var i = 0; i < this.pos.length; i++) {
		var p1 = this.pos[i].p;
		var p2 = this.pos[(i + 1) % this.pos.length].p;
		if (p2[1] != p1[1]) {
			this.pos[i].fy = (function() {
				var a = (p2[0] - p1[0]) / (p2[1] - p1[1]);
				var b = (p1[1] * p2[0] - p1[0] * p2[1]) / (p2[1] - p1[1]);
				return function(in_y) {
					return a * in_y - b;
				};
			})();
		} else {
			this.pos[i].fy = null;
		}
	}
}

cPosList.prototype.separateRect = function(in_dim)
{
	var v = [
		{
			x : this.v[0].x / in_dim,
			y : this.v[0].y / in_dim
		},
		{
			x : this.v[1].x / in_dim,
			y : this.v[1].y / in_dim
		}
	];
	var ret = [];
	for (var y = 0; y < in_dim; y++) {
		ret[y] = [];
		for (var x = 0; x < in_dim; x++) {
			ret[y][x] = new cPosList(
				this.pos[0].p[0] + x * v[0].x + y * v[1].x,
				this.pos[0].p[1] + x * v[0].y + y * v[1].y,
				v[0],
				v[1]
			);
		}
	}
	return ret;
};

cPosList.prototype.drawPolygon = function(ctx, color, bordercolor)
{
	ctx.beginPath();
	for (var i = 0; i < this.pos.length; i++) {
		var p = this.pos[i].p;
		if (i == 0) {
			ctx.moveTo(p[0], p[1]);
		} else {
			ctx.lineTo(p[0], p[1]);
		}
	}
	ctx.closePath();
	ctx.strokeStyle = bordercolor;
	ctx.stroke();
	if (color) {
		ctx.fillStyle = color;
		ctx.fill();
	}
};

cPosList.prototype.drawRichStyle = function(ctx, color)
{
	ctx.fillStyle = 'black';
	ctx.fillRect(this.pos[0].p[0], this.pos[0].p[1], 1, 1);
	ctx.fillRect(this.pos[1].p[0]-1, this.pos[1].p[1], 1, 1);
	ctx.fillRect(this.pos[2].p[0]-1, this.pos[2].p[1]-1, 1, 1);
	ctx.fillRect(this.pos[3].p[0], this.pos[3].p[1]-1, 1, 1);
};

cPosList.prototype.isIn = function(x, y)
{
	var cnt = 0;
	for (var i = 0; i < this.pos.length; i++) {
		var p1 = this.pos[i].p;
		var p2 = this.pos[(i + 1) % this.pos.length].p;
		if (p2[1] != p1[1]) {
			var crossX = this.pos[i].fy(y);
			if (crossX < x) {
				continue;
			}
			if (p1[0] == p2[0]) {
				if ((y - p1[1]) * (y - p2[1]) < 0) {
					cnt++;
				}
			} else {
				if ((crossX - p1[0]) * (crossX - p2[0]) < 0) {
					cnt++;
				}
			}
		}
	}
	return (cnt % 2 == 1);
};

function BIND(callback, self)
{
	return function() {
		callback.apply(self, arguments);
	}
}

var RECT = Math.min(window.innerWidth, window.innerHeight);

var gCanvas = {
	size : -1,
	canvas : null,
	m : 1,
	r : 8,
	sW : (RECT / 5),
	sH : (RECT / 5),
	rW : (RECT / 2),
	rH : (RECT / 2),
	view : null,
	vtbl : {
		LT : {main : null, L : null, T : null},
		TR : {main : null, T : null, R : null},
		RB : {main : null, R : null, B : null},
		BL : {main : null, B : null, L : null}
	},
	colors : ['yellow', 'white', 'orange', 'green', 'blue', 'red'],
	drawPiece : function(in_pi) {
		/*
			in_pi = {
				T : [][],
				R : [][]
			} or
			in_pi = {
				R : [][],
				B : [][]
			} or
			in_pi = {
				B : [][],
				L : [][]
			} or
			in_pi = {
				L : [][],
				T : [][]
			}
		*/
		var ctx = this.canvas.getContext('2d');
		for (var dir in this.vtbl[this.view]) {
			for (var y = 0; y < this.size; y++) {
				for (var x = 0; x < this.size; x++) {
					this.vtbl[this.view][dir][y][x].drawPolygon(ctx, this.colors[in_pi[dir][y][x]], 'black');
					this.vtbl[this.view][dir][y][x].drawRichStyle(ctx, this.colors[in_pi[dir][y][x]]);
				}
			}
		}
	},
	getPos : function(in_target, in_x, in_y) {
		for (var y = 0; y < this.size; y++) {
			for (var x = 0; x < this.size; x++) {
				if (this.vtbl[this.view][in_target][y][x].isIn(in_x, in_y)) {
					return {x : x, y : y};
				}
			}
		}
		return null;
	},
	mouse : {},
	onmouseup : function(e) {
		for (var dir in this.mouse) {
			if (!this.mouse[dir]) {
				continue;
			}
			var p = this.getPos(dir, e.clientX, e.clientY);
			if (!p) {
				/* change this.view */
				if (dir == 'main') {
					var test = {
						'TR' : {'T' : 'RB', 'R' : 'LT'},
						'RB' : {'R' : 'BL', 'B' : 'TR'},
						'BL' : {'B' : 'LT', 'L' : 'RB'},
						'LT' : {'L' : 'TR', 'T' : 'BL'}
					}[this.view];
					for (var change in test) {
						if (this.getPos(change, e.clientX, e.clientY)) {
							this.view = test[change];
							this.update();
							break;
						}
					}
				}
				break;
			}
			if ((this.mouse[dir].x == p.x) && (this.mouse[dir].y == p.y)) {
				break;
			}
			var lro = [
				{
					X  : true,
					Y  : false,
					px : p.x,
					py : p.y,
					dx : (p.x - this.mouse[dir].x > 0) ? 1 : -1,
					dy : (p.y - this.mouse[dir].y > 0) ? 1 : -1
				},
				{
					X  : false,
					Y  : true,
					px : p.x,
					py : this.size - 1 - p.y,
					dx : (p.x - this.mouse[dir].x > 0) ? 1 : -1,
					dy : (p.y - this.mouse[dir].y > 0) ? -1 : 1
				},
				{
					/* dimmy */
				},
				{
					X  : false,
					Y  : true,
					px : this.size - 1 - p.x,
					py : p.y,
					dx : (p.x - this.mouse[dir].x > 0) ? -1 : 1,
					dy : (p.y - this.mouse[dir].y > 0) ? 1 : -1
				}
			];
			if (dir == 'main') {
				if (this.mouse[dir].x == p.x) {
					cube.rotate(0, lro[0].X, lro[0].px, lro[0].dy);
				}
				if (this.mouse[dir].y == p.y) {
					cube.rotate(0, lro[0].Y, lro[0].py, lro[0].dx);
				}
			} else {
				var ret = cube.dir2dim(0, dir);
				if (!ret) {
					continue;
				}
				if (this.mouse[dir].x == p.x) {
					cube.rotate(ret.dim, lro[ret.lro].X, lro[ret.lro].px, lro[ret.lro].dy);
				}
				if (this.mouse[dir].y == p.y) {
					cube.rotate(ret.dim, lro[ret.lro].Y, lro[ret.lro].py, lro[ret.lro].dx);
				}
			}
			this.update();
		}
		this.mouse = {};
	},
	onmousedown : function(e) {
		this.mouse = {};
		for (var dir in this.vtbl[this.view]) {
			this.mouse[dir] = this.getPos(dir, e.clientX, e.clientY);
			if (this.mouse[dir]) {
				return;
			}
		}
	},
	_update : function(in_pi) {
		/*
			in_pi = {
				main : [][],
				T : [][],
				R : [][],
				B : [][],
				L : [][]
			}
		*/
		var ctx = this.canvas.getContext('2d');
		ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
		var pi = {};
		for (var dir in this.vtbl[this.view]) {
			pi[dir] = in_pi[dir];
		}
		this.drawPiece(pi);
	},
	update : function() {
		this._update(cube.getPieceData4View(0, 'main'));
	},
	init : function(in_size) {
		with (this) {
			size = in_size;
			view = 'LT';
			/* left & top */
			vtbl.LT.main = (new cPosList(m+sW+r,	m+sH+r, {x : rW, y :  -r},	{x :  -r, y : rH})).separateRect(in_size);
			vtbl.LT.L    = (new cPosList(m+r,		m+r,	{x : sW, y :  sH},	{x :  -r, y : rH})).separateRect(in_size);
			vtbl.LT.T    = (new cPosList(m+r,		m+r,	{x : rW, y :  -r},	{x :  sW, y : sH})).separateRect(in_size);
			/* right & bottom */
			vtbl.RB.main = (new cPosList(m+r,		m+r, 	{x : rW, y :  -r},	{x :  -r, y : rH})).separateRect(in_size);
			vtbl.RB.R    = (new cPosList(m+rW+r,	m, 		{x : sW, y :  sH},	{x :  -r, y : rH})).separateRect(in_size);
			vtbl.RB.B    = (new cPosList(m,			m+rW+r,	{x : rW, y :  -r},	{x :  sW, y : sH})).separateRect(in_size);
			/* top & right */
			vtbl.TR.main = (new cPosList(m,			m+sH, 	{x : rW, y :   r},	{x :   r, y : rH})).separateRect(in_size);
			vtbl.TR.T    = (new cPosList(m+sW,		m,		{x : rW, y :   r},	{x : -sW, y : sH})).separateRect(in_size);
			vtbl.TR.R    = (new cPosList(m+rW,		m+sH+r, {x : sW, y : -sH},	{x :   r, y : rH})).separateRect(in_size);
			/* bottom & left */
			vtbl.BL.main = (new cPosList(m+sW,		m, 		{x : rW, y :   r},	{x :   r, y : rH})).separateRect(in_size);
			vtbl.BL.B    = (new cPosList(m+sW+r,	m+rW,	{x : rW, y :   r},	{x : -sW, y : sH})).separateRect(in_size);
			vtbl.BL.L    = (new cPosList(m,			m+sH,	{x : sW, y : -sH},	{x :   r, y : rH})).separateRect(in_size);
			canvas = document.createElement('CANVAS');
			canvas.width  = rW + sW + (m + r) * 2;
			canvas.height = rH + sH + (m + r) * 2;
		}
		var ev = ['onmouseup', 'onmousedown'];
		for (var i = 0; i < ev.length; i++) {
			this.canvas[ev[i]] = BIND(this[ev[i]], this);
		}
		return this.canvas;
	}
};

var SIZE = 0;
if (location.search.match(/([2-9])/,'')) {
	SIZE = Number(RegExp.$1);
} else {
	SIZE = 3;
}
if (SIZE > 0) {
	cube.createCube(SIZE);
	document.getElementById('debug').appendChild(gCanvas.init(SIZE));
	cube.shuffle(100);
	gCanvas.update();
}

</script>
<div><a href='cube.html?2'>2x2</a></div>
<div><a href='cube.html?3'>3x3</a></div>
<div><a href='cube.html?4'>4x4</a></div>
<div><a href='cube.html?5'>5x5</a></div>
<div><a href='http://blogs.yahoo.co.jp/ichiro_hiroshi/folder/9429.html?m=l'>products</a></div>
<style type='text/css'>
BODY {background-color: black}
#debug {overflow: hidden; background-color: black}
#debug TD {border: solid 1px black; width: 16px; height: 16px; padding: 0px; margin: 0px;}
#debug .c0 {background-color: yellow;}
#debug .c1 {background-color: red;}
#debug .c2 {background-color: orange;}
#debug .c3 {background-color: blue;}
#debug .c4 {background-color: green;}
#debug .c5 {background-color: white;}
</style>
